import type { ArrayBufferObject, FunctionObject } from './abstract-ops/all.mts';
import type { AbstractModuleRecord } from './modules.mts';
import type {
  BooleanValue, JSStringValue, NumberValue, ObjectValue, PropertyKeyValue,
} from './value.mts';
import { inspect, PrivateName, Value } from './index.mts';

function i(V: unknown) {
  if (V instanceof Value) {
    return inspect(V);
  }
  if (V instanceof PrivateName) {
    return `#${V.Description.stringValue()}`;
  }
  return `${V}`;
}

export const Raw = <const S, >(s: S) => s;

export const AlreadyDeclared = (n: JSStringValue | PrivateName | string) => `${i(n)} is already declared`;
export const ArrayBufferDetached = () => 'Attempt to access detached ArrayBuffer';
export const ArrayBufferShared = () => 'Attempt to access shared ArrayBuffer';
export const ArrayPastSafeLength = () => 'Cannot make length of array-like object surpass the bounds of an integer index';
export const ArrayEmptyReduce = () => 'Cannot reduce an empty array with no initial value';
export const AssignmentToConstant = (n: JSStringValue) => `Assignment to constant variable ${i(n)}`;
export const AwaitInFormalParameters = () => 'await is not allowed in function parameters';
export const AwaitInClassStaticBlock = () => 'await is not allowed in class static blocks';
export const AwaitNotInAsyncFunction = () => 'await is only valid in async functions';
export const BigIntDivideByZero = () => 'Division by zero';
export const BigIntNegativeExponent = () => 'Exponent must be positive';
export const BigIntLiteralCannotLeadingZero = () => 'BigInt literal cannot have leading zero.';
export const BigIntUnsignedRightShift = () => 'BigInt has no unsigned right shift, use >> instead';
export const BufferContentTypeMismatch = () => 'Newly created TypedArray did not match exemplar\'s content type';
export const BufferDetachKeyMismatch = (k: Value, b: ArrayBufferObject) => `${i(k)} is not the [[ArrayBufferDetachKey]] of ${i(b)}`;
export const CannotAllocateDataBlock = () => 'Cannot allocate memory';
export const CannotCreateProxyWith = (x: string, y: string) => `Cannot create a proxy with a ${x} as ${y}`;
export const CannotConstructAbstractFunction = (c: FunctionObject) => `Cannot construct abstract ${i(c)}`;
export const CannotConvertDecimalToBigInt = (n: NumberValue) => `Cannot convert ${i(n)} to a BigInt because it is not an integer`;
export const CannotConvertSymbol = (t: string) => `Cannot convert a Symbol value to a ${t}`;
export const CannotConvertToBigInt = (v: Value) => `Cannot convert ${i(v)} to a BigInt`;
export const CannotConvertToObject = (t: 'null' | 'undefined') => `Cannot convert ${t} to object`;
export const CannotDefineProperty = (p: PropertyKeyValue) => `Cannot define property ${i(p)}`;
export const CannotDeleteProperty = (p: PropertyKeyValue) => `Cannot delete property ${i(p)}`;
export const CannotDeleteSuper = () => 'Cannot delete a super property';
export const CannotJSONSerializeBigInt = () => 'Cannot serialize a BigInt to JSON';
export const CannotMixBigInts = () => 'Cannot mix BigInt and other types, use explicit conversions';
export const CannotResolvePromiseWithItself = () => 'Cannot resolve a promise with itself';
export const CannotSetProperty = (p: PropertyKeyValue, o: unknown) => `Cannot set property ${i(p)} on ${i(o)}`;
export const ClassMissingBindingIdentifier = () => 'Class declaration missing binding identifier';
export const ConstDeclarationMissingInitializer = () => 'Missing initialization of const declaration';
export const ConstructorNonCallable = (f: Value) => `${i(f)} cannot be invoked without new`;
export const CouldNotResolveModule = (s: JSStringValue | string, from?: string) => `Could not resolve module ${i(s)} from ${from ? i(from) : 'no referrer'}`;
export const DataViewOOB = () => 'Offset is outside the bounds of the DataView';
export const DeferredModuleNotReady = (m: AbstractModuleRecord) => `Module ${m.HostDefined?.specifier ?? ''} is not ready for synchronous execution`;
export const DeleteIdentifier = () => 'Delete of identifier in strict mode';
export const DeletePrivateName = () => 'Private fields cannot be deleted';
export const DateInvalidTime = () => 'Invalid time';
export const DerivedConstructorReturnedNonObject = () => 'Derived constructors may only return object or undefined';
export const DuplicateConstructor = () => 'A class may only have one constructor';
export const DuplicateExports = () => 'Module cannot contain duplicate exports';
export const DuplicateImportAttribute = (a: string) => `Duplicate import attribute ${i(a)}`;
export const DuplicateProto = () => 'An object literal may only have one __proto__ property';
export const FunctionDeclarationStatement = () => 'Functions can only be declared at top level or inside a block';
export const GeneratorRunning = () => 'Cannot manipulate a running generator';
export const LegacyOctalLiteralInStrictMode = () => 'Legacy octal literals are not allowed in strict mode';
export const IllegalBreakContinue = (isBreak: boolean) => `Illegal ${isBreak ? 'break' : 'continue'} statement`;
export const IllegalOctalEscape = () => 'Illegal octal escape';
export const InternalSlotMissing = (_o: ObjectValue, s: string) => `Internal slot ${s} is missing`;
export const InvalidArrayLength = (l: Value | number) => `Invalid array length: ${i(l)}`;
export const InvalidAssignmentTarget = () => 'Invalid assignment target';
export const InvalidCodePoint = () => 'Not a valid code point';
export const InvalidHint = (v: Value) => `Invalid hint: ${i(v)}`;
export const InvalidMethodName = (name: string) => `Method cannot be named '${i(name)}'`;
export const InvalidPropertyDescriptor = () => 'Invalid property descriptor. Cannot both specify accessors and a value or writable attribute';
export const InvalidRadix = () => 'Radix must be between 2 and 36, inclusive';
export const InvalidReceiver = (f: string, v: Value) => `${f} called on invalid receiver: ${i(v)}`;
export const InvalidRegExpFlags = (f: string) => `Invalid RegExp flags: ${f}`;
export const RegExpFlagsCannotUseTogether = (f1: string, f2: string) => `Cannot use RegExp flags ${f1} and ${f2} together`;
export const InvalidSuperCall = () => '`super` not expected here';
export const InvalidSuperProperty = () => '`super` not expected here';
export const InvalidTemplateEscape = () => 'Invalid escapes are only allowed in tagged templates';
export const InvalidThis = () => 'Invalid `this` access';
export const InvalidUnicodeEscape = () => 'Invalid unicode escape';
export const InvalidAlphabet = () => 'Invalid alphabet';
export const InvalidLastChunkHandling = () => 'Invalid lastChunkHandling';
export const InvalidBase64String = () => 'Invalid base64 string';
export const InvalidHexString = () => 'Invalid hex string';
export const IteratorCompleted = () => 'The iterator is already complete.';
export const IteratorThrowMissing = () => 'The iterator does not provide a throw method';
export const JSONCircular = () => 'Cannot JSON stringify a circular structure';
export const JSONUnexpectedToken = () => 'Unexpected token in JSON';
export const JSONUnexpectedChar = (c: string | null) => `Unexpected character ${c} in JSON`;
export const JSONExpected = (e: string | readonly string[], a: string | null) => `Expected character ${e} but got ${a} in JSON`;
export const LetInLexicalBinding = () => '\'let\' is not allowed to be used as a name in lexical declarations';
export const ModuleExportNameInvalidUnicode = () => 'Export name is not valid unicode';
export const ModuleUndefinedExport = (n: string) => `Export '${i(n)}' is not defined in module`;
export const NegativeIndex = (n: string) => `${n} cannot be negative`;
export const NewlineAfterThrow = () => 'Illegal newline after throw';
export const NormalizeInvalidForm = () => 'Invalid normalization form';
export const NotAConstructor = (v: Value | string) => `${i(v)} is not a constructor`;
export const NotAFunction = (v: Value | string) => `${i(v)} is not a function`;
export const NotATypeObject = (t: string, v: Value) => `${i(v)} is not a ${t} object`;
export const NotAnObject = (v: Value) => `${i(v)} is not an object`;
export const NotASymbol = (v: Value) => `${i(v)} is not a symbol`;
export const NotAWeakKey = (v: Value) => `${i(v)} is not an object or a symbol`;
export const NotAString = (v: Value) => `${i(v)} is not a string`;
export const NotANumber = (v: Value) => `${i(v)} is not a number`;
export const NotDefined = (n: PrivateName | Value | string) => `${i(n)} is not defined`;
export const NotEnoughArguments = (numArgs: number, minArgs: number) => `${minArgs} argument${minArgs !== 1 ? 's' : ''} required, but only ${numArgs} present`;
export const NotInitialized = (n: JSStringValue) => `${i(n)} cannot be used before initialization`;
export const NotIterable = (n: Value) => `${i(n)} is not iterable`;
export const NotPropertyName = (p: Value) => `${i(p)} is not a valid property name`;
export const NotUint8Array = () => 'Not a Uint8Array';
export const NumberFormatRange = (m: string) => `Invalid format range for ${m}`;
export const ObjectToPrimitive = () => 'Cannot convert object to primitive value';
export const ObjectPrototypeType = () => 'Object prototype must be an Object or null';
export const ObjectSetPrototype = () => 'Could not set prototype of object';
export const OutOfRange = (n: Value | string | number) => `${i(n)} is out of range`;
export const PrivateNameNoGetter = (p: PrivateName) => `${i(p)} was defined without a getter`;
export const PrivateNameNoSetter = (p: PrivateName) => `${i(p)} was defined without a setter`;
export const PrivateNameIsMethod = (p: PrivateName) => `Private method ${i(p)} is not writable`;
export const PromiseAnyRejected = () => 'No promises passed to Promise.any were fulfilled';
export const PromiseCapabilityFunctionAlreadySet = (f: 'resolve' | 'reject') => `Promise ${f} function already set`;
export const PromiseRejectFunction = (v: Value) => `Promise reject function ${i(v)} is not callable`;
export const PromiseResolveFunction = (v: Value) => `Promise resolve function ${i(v)} is not callable`;
export const ProxyRevoked = (n: string) => `Cannot perform '${n}' on a proxy that has been revoked`;
export const ProxyDefinePropertyNonConfigurable = (p: PropertyKeyValue) => `'defineProperty' on proxy: trap returned truthy for defining non-configurable property ${i(p)} which is either non-existent or configurable in the proxy target`;
export const ProxyDefinePropertyNonConfigurableWritable = (p: PropertyKeyValue) => `'defineProperty' on proxy: trap returned truthy for defining non-configurable property ${i(p)} which cannot be non-writable, unless there exists a corresponding non-configurable, non-writable own property of the target object`;
export const ProxyDefinePropertyNonExtensible = (p: PropertyKeyValue) => `'defineProperty' on proxy: trap returned truthy for adding property ${i(p)} to the non-extensible proxy target`;
export const ProxyDefinePropertyIncompatible = (p: PropertyKeyValue) => `'defineProperty' on proxy: trap returned truthy for adding property ${i(p)} that is incompatible with the existing property in the proxy target`;
export const ProxyDeletePropertyNonConfigurable = (p: PropertyKeyValue) => `'deleteProperty' on proxy: trap returned truthy for property ${i(p)} which is non-configurable in the proxy target`;
export const ProxyDeletePropertyNonExtensible = (p: PropertyKeyValue) => `'deleteProperty' on proxy: trap returned truthy for property ${i(p)} but the proxy target is non-extensible`;
export const ProxyGetNonConfigurableData = (p: PropertyKeyValue) => `'get' on proxy: property ${i(p)} is a read-only and non-configurable data property on the proxy target but the proxy did not return its actual value`;
export const ProxyGetNonConfigurableAccessor = (p: PropertyKeyValue) => `'get' on proxy: property ${i(p)} is a non-configurable accessor property on the proxy target and does not have a getter function, but the trap did not return 'undefined'`;
export const ProxyGetPrototypeOfInvalid = () => '\'getPrototypeOf\' on proxy: trap returned neither object nor null';
export const ProxyGetPrototypeOfNonExtensible = () => '\'getPrototypeOf\' on proxy: proxy target is non-extensible but the trap did not return its actual prototype';
export const ProxyGetOwnPropertyDescriptorIncompatible = (p: PropertyKeyValue) => `'getOwnPropertyDescriptor' on proxy: trap returned descriptor for property ${i(p)} that is incompatible with the existing property in the proxy target`;
export const ProxyGetOwnPropertyDescriptorInvalid = (p: PropertyKeyValue) => `'getOwnPropertyDescriptor' on proxy: trap returned neither object nor undefined for property ${i(p)}`;
export const ProxyGetOwnPropertyDescriptorUndefined = (p: PropertyKeyValue) => `'getOwnPropertyDescriptor' on proxy: trap returned undefined for property ${i(p)} which is non-configurable in the proxy target`;
export const ProxyGetOwnPropertyDescriptorNonExtensible = (p: PropertyKeyValue) => `'getOwnPropertyDescriptor' on proxy: trap returned undefined for property ${i(p)} which exists in the non-extensible target`;
export const ProxyGetOwnPropertyDescriptorNonConfigurable = (p: PropertyKeyValue) => `'getOwnPropertyDescriptor' on proxy: trap reported non-configurability for property ${i(p)} which is either non-existent or configurable in the proxy target`;
export const ProxyGetOwnPropertyDescriptorNonConfigurableWritable = (p: PropertyKeyValue) => `'getOwnPropertyDescriptor' on proxy: trap reported non-configurability for property ${i(p)} which is writable or configurable in the proxy target`;
export const ProxyHasNonConfigurable = (p: PropertyKeyValue) => `'has' on proxy: trap returned falsy for property ${i(p)} which exists in the proxy target as non-configurable`;
export const ProxyHasNonExtensible = (p: PropertyKeyValue) => `'has' on proxy: trap returned falsy for property ${i(p)} but the proxy target is not extensible`;
export const ProxyIsExtensibleInconsistent = (e: BooleanValue) => `'isExtensible' on proxy: trap result does not reflect extensibility of proxy target (which is ${i(e)})`;
export const ProxyOwnKeysMissing = (p: string) => `'ownKeys' on proxy: trap result did not include ${i(p)}`;
export const ProxyOwnKeysNonExtensible = () => '\'ownKeys\' on proxy: trap result returned extra keys but proxy target is non-extensible';
export const ProxyOwnKeysDuplicateEntries = () => '\'ownKeys\' on proxy: trap returned duplicate entries';
export const ProxyPreventExtensionsExtensible = () => '\'preventExtensions\' on proxy: trap returned truthy but the proxy target is extensible';
export const ProxySetPrototypeOfNonExtensible = () => '\'setPrototypeOf\' on proxy: trap returned truthy for setting a new prototype on the non-extensible proxy target';
export const ProxySetFrozenData = (p: PropertyKeyValue) => `'set' on proxy: trap returned truthy for property ${i(p)} which exists in the proxy target as a non-configurable and non-writable data property with a different value`;
export const ProxySetFrozenAccessor = (p: PropertyKeyValue) => `'set' on proxy: trap returned truthy for property ${i(p)} which exists in the proxy target as a non-configurable and non-writable accessor property without a setter`;
export const RegExpArgumentNotAllowed = (m: string) => `First argument to ${m} must not be a regular expression`;
export const RegExpExecNotObject = (o: Value) => `${i(o)} is not object or null`;
export const ResizableBufferInvalidMaxByteLength = () => 'Invalid maxByteLength for resizable ArrayBuffer';
export const ResolutionNullOrAmbiguous = (r: string | null, n: Value, m: AbstractModuleRecord) => (r === null
  ? `Could not resolve import ${i(n)} from ${m.HostDefined.specifier}`
  : `Star export ${i(n)} from ${m.HostDefined.specifier} is ambiguous`);
export const SizeIsNaN = () => 'size property must not be undefined, as it will be NaN';
export const SeparatorIsNotAllowed = () => 'Numeric separators are not allowed here';
export const SizeMustBePositiveInteger = () => 'size property must be a positive integer';
export const SpeciesNotConstructor = () => 'object.constructor[Symbol.species] is not a constructor';
export const StrictModeDelete = (n: PropertyKeyValue) => `Cannot not delete property ${i(n)}`;
export const StrictPoisonPill = () => 'The caller, callee, and arguments properties may not be accessed on functions or the arguments objects for calls to them';
export const StringRepeatCount = (v: number) => `Count ${i(v)} is invalid`;
export const StringCodePointInvalid = (n: Value) => `Invalid code point ${i(n)}`;
export const StringPrototypeMethodGlobalRegExp = (m: string) => `The RegExp passed to String.prototype.${m} must have the global flag`;
export const SubclassLengthTooSmall = (v: ArrayBufferObject) => `Subclass constructor returned a smaller-than-requested object ${i(v)}`;
export const SubclassSameValue = (v: ArrayBufferObject) => `Subclass constructor returned the same object ${i(v)}`;
export const TargetMatchesHeldValue = (v: Value) => `heldValue ${i(v)} matches target`;
export const TemplateInOptionalChain = () => 'Templates are not allowed in optional chains';
export const ThisNotAFunction = (v: Value) => `Expected 'this' value to be a function but got ${i(v)}`;
export const TryMissingCatchOrFinally = () => 'Missing catch or finally after try';
export const TypedArrayCreationOOB = () => 'Sum of start offset and byte length should be less than the size of underlying buffer';
export const TypedArrayLengthAlignment = (n: number, m: number) => `Size of ${n} should be a multiple of ${m}`;
export const TypedArrayOOB = () => 'Sum of start offset and byte length should be less than the size of the TypedArray';
export const TypedArrayOutOfBounds = () => 'TypedArray index out of bounds';
export const TypedArrayOffsetAlignment = (n: number, m: number) => `Start offset of ${n} should be a multiple of ${m}`;
export const TypedArrayTooSmall = () => 'Derived TypedArray constructor created an array which was too small';
export const UnableToSeal = (o: ObjectValue) => `Unable to seal object ${i(o)}`;
export const UnableToFreeze = (o: ObjectValue) => `Unable to freeze object ${i(o)}`;
export const UnableToPreventExtensions = (o: ObjectValue) => `Unable to prevent extensions on object ${i(o)}`;
export const UnknownPrivateName = (o: ObjectValue, p: PrivateName) => `${i(p)} does not exist on object ${i(o)}`;
export const UnsupportedImportAttribute = (key: JSStringValue) => `Unsupported import attribute ${i(key)}`;
export const UnsupportedModuleType = (type: string) => `Unsupported module type ${i(type)} (only "json" is supported)`;
export const UnterminatedComment = () => 'Missing */ after comment';
export const UnterminatedRegExp = () => 'Missing / after RegExp literal';
export const UnterminatedString = () => 'Missing \' or " after string literal';
export const UnterminatedTemplate = () => 'Missing ` after template literal';
export const UnexpectedEOS = () => 'Unexpected end of source';
export const UnexpectedEvalOrArguments = () => '`arguments` and `eval` are not valid in this context';
export const UnexpectedToken = () => 'Unexpected token';
export const UnexpectedReservedWordStrict = () => 'Unexpected reserved word in strict mode';
export const UseStrictNonSimpleParameter = () => 'Function with \'use strict\' directive has non-simple parameter list';
export const URIMalformed = () => 'URI malformed';
export const WeakCollectionNotObject = (v: Value) => `${i(v)} is not a valid weak collection entry object`;
export const YieldInFormalParameters = () => 'yield is not allowed in function parameters';
export const YieldNotInGenerator = () => 'yield is only valid in generators';
